#!/usr/bin/env python
#
# Copyright 2012-2016 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

from __future__ import print_function

import sys, time, re, pprint
import random,math,operator
try:
    import networkx as nx
    import matplotlib
    matplotlib.use("QT4Agg")
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

    # Manage different matplotlib versions
    try:
        from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
    except ImportError:
        try:
            from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
        except ImportError:
            print(sys.argv[0], "could not load QTAgg backend.")
            sys.exit(1)


    from matplotlib.figure import Figure
except ImportError:
    print(sys.argv[0], "requires networkx and matplotlib.",
       "Please check that they are installed and try again.")
    sys.exit(1)

from PyQt4 import QtCore,Qt
import PyQt4.QtGui as QtGui
import itertools

from gnuradio import gr, ctrlport
from gnuradio.ctrlport.GrDataPlotter import *

#check for networkx version
_critical_version = (1,11)
_atleast_critical = False

for act,ref in zip(nx.__version__.split("."), _critical_version):
    _atleast_critical = (act >= ref)
    if not act == ref:
        break

if _atleast_critical:
    from networkx.drawing.nx_agraph import graphviz_layout
else:
    graphviz_layout = nx.graphviz_layout

class MAINWindow(QtGui.QMainWindow):
    def minimumSizeHint(self):
        return QtGui.QSize(800,600)

    def __init__(self, radioclient):

        super(MAINWindow, self).__init__()
        self.radioclient = radioclient
        self.conns = []
        self.plots = []
        self.knobprops = []

        self.mdiArea = QtGui.QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.windowMapper = QtCore.QSignalMapper(self)
        self.windowMapper.mapped[QtGui.QWidget].connect(self.setActiveSubWindow)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.setWindowTitle("GNU Radio Performance Monitor")
        self.setUnifiedTitleAndToolBarOnMac(True)

        self.newCon(radioclient)

        icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
        self.setWindowIcon(icon)

    def newSubWindow(self, window, title):
        child = window;
        child.setWindowTitle(title)
        self.mdiArea.addSubWindow(child)
        self.conns.append(child)
        child.show();
        self.mdiArea.currentSubWindow().showMaximized()

    def newCon(self, csomeBool):
        child = MForm(self.radioclient, len(self.conns), self, dialogprompt = not csomeBool)
        if(child.radioclient is not None):
            child.setWindowTitle(str(child.radioclient))
            self.mdiArea.addSubWindow(child)
            self.mdiArea.currentSubWindow().showMaximized()

        self.conns.append(child)
        self.plots.append([])

    def update(self, knobs, uid):
        #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
        for plot in self.plots[uid]:
            data = knobs[plot.name()].value
            plot.update(data)
            plot.stop()
            plot.wait()
            plot.start()

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)


    def createActions(self):
        self.newConAct = QtGui.QAction("&New Connection",
                self, shortcut=QtGui.QKeySequence.New,
                statusTip="Create a new file", triggered=self.newCon)

        self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
                statusTip="Exit the application",
                triggered=QtGui.qApp.closeAllWindows)

        self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
                statusTip="Close the active window",
                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = QtGui.QAction("Close &All", self,
                statusTip="Close all the windows",
                triggered=self.mdiArea.closeAllSubWindows)

        qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
        self.tileAct = QtGui.QAction("&Tile", self,
                statusTip="Tile the windows",
                triggered=self.mdiArea.tileSubWindows,
                shortcut=qks)

        qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
        self.cascadeAct = QtGui.QAction("&Cascade", self,
                statusTip="Cascade the windows", shortcut=qks,
                triggered=self.mdiArea.cascadeSubWindows)

        self.nextAct = QtGui.QAction("Ne&xt", self,
                shortcut=QtGui.QKeySequence.NextChild,
                statusTip="Move the focus to the next window",
                triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QtGui.QAction("Pre&vious", self,
                shortcut=QtGui.QKeySequence.PreviousChild,
                statusTip="Move the focus to the previous window",
                triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QtGui.QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = QtGui.QAction("&About", self,
                statusTip="Show the application's About box",
                triggered=self.about)

        self.aboutQtAct = QtGui.QAction("About &Qt", self,
                statusTip="Show the Qt library's About box",
                triggered=QtGui.qApp.aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newConAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newConAct)

        self.fileToolBar = self.addToolBar("Window")
        self.fileToolBar.addAction(self.tileAct)
        self.fileToolBar.addAction(self.cascadeAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")


    def activeMdiChild(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        self.closeAct.setEnabled(hasMdiChild)
        self.closeAllAct.setEnabled(hasMdiChild)
        self.tileAct.setEnabled(hasMdiChild)
        self.cascadeAct.setEnabled(hasMdiChild)
        self.nextAct.setEnabled(hasMdiChild)
        self.previousAct.setEnabled(hasMdiChild)
        self.separatorAct.setVisible(hasMdiChild)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tileAct)
        self.windowMenu.addAction(self.cascadeAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

    def about(self):
        about_info = \
'''Copyright 2012 Free Software Foundation, Inc.\n
This program is part of GNU Radio.\n
GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''

        QtGui.QMessageBox.about(None, "gr-perf-monitorx", about_info)


class ConInfoDialog(QtGui.QDialog):
    def __init__(self, parent=None):
        super(ConInfoDialog, self).__init__(parent)

        self.gridLayout = QtGui.QGridLayout(self)


        self.host = QtGui.QLineEdit(self);
        self.port = QtGui.QLineEdit(self);
        self.host.setText("localhost");
        self.port.setText("43243");

        self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
                                                QtGui.QDialogButtonBox.Cancel)

        self.gridLayout.addWidget(self.host);
        self.gridLayout.addWidget(self.port);
        self.gridLayout.addWidget(self.buttonBox);

        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)


    def accept(self):
        self.done(1);

    def reject(self):
        self.done(0);


class DataTable(QtGui.QWidget):
    def update(self):
        print("update")

    def closeEvent(self, event):
        self.timer = None

    def __init__(self, radioclient, G):
        QtGui.QWidget.__init__( self)

        self.layout = QtGui.QVBoxLayout(self)
        self.hlayout = QtGui.QHBoxLayout()
        self.layout.addLayout(self.hlayout)

        self.G = G
        self.radioclient = radioclient

        self._keymap = None

        self.disp = None

        # Create a combobox to set the type of statistic we want.
        self._statistic = "Instantaneous"
        self._statistics_table = {"Instantaneous": "",
                                  "Average": "avg ",
                                  "Variance": "var "}
        self.stattype = QtGui.QComboBox()
        self.stattype.addItem("Instantaneous")
        self.stattype.addItem("Average")
        self.stattype.addItem("Variance")
        self.stattype.setMaximumWidth(200)
        self.hlayout.addWidget(self.stattype)
        self.stattype.currentIndexChanged.connect(self.stat_changed)

        # Create a checkbox to toggle sorting of graphs
        self._sort = False
        self.checksort = QtGui.QCheckBox("Sort")
        self.checksort.setCheckState(self._sort)
        self.hlayout.addWidget(self.checksort);
        self.checksort.stateChanged.connect(self.checksort_changed)

        # set up table
        self.perfTable = Qt.QTableWidget()
        self.perfTable.setColumnCount(2)
        self.perfTable.verticalHeader().hide()
        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Runtime"] )
        self.perfTable.horizontalHeader().setStretchLastSection(True)
        self.perfTable.setSortingEnabled(True)
        nodes = self.G.nodes(data=True)

        # set up plot
        self.f = plt.figure(figsize=(10,8), dpi=90)
        self.sp = self.f.add_subplot(111)
        self.sp.autoscale_view(True,True,True)
        self.sp.set_autoscale_on(True)

        # set up tabs
        self.tabber = QtGui.QTabWidget();
        self.layout.addWidget(self.tabber);
        self.tabber.addTab(self.perfTable,"Table View");
        self.tabber.addTab(self.f.canvas, "Graph View");

        # set up timer
        self.timer = QtCore.QTimer()
        self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
        self.timer.start(500)

        for i in range(0,len(nodes)):
            self.perfTable.setItem(
                i,0,
                Qt.QTableWidgetItem(nodes[i][0]))

    def table_update(self,data):
        for k in data.keys():
            weight = data[k]
            existing = self.perfTable.findItems(str(k),QtCore.Qt.MatchFixedString)
            if(len(existing) == 0):
                i = self.perfTable.rowCount();
                self.perfTable.setRowCount( i+1)
                self.perfTable.setItem( i,0, Qt.QTableWidgetItem(str(k)))
                self.perfTable.setItem( i,1, Qt.QTableWidgetItem(str(weight)))
            else:
                self.perfTable.setItem( self.perfTable.row(existing[0]),1, Qt.QTableWidgetItem(str(weight)))

    def stat_changed(self, index):
        self._statistic = str(self.stattype.currentText())

    def checksort_changed(self, state):
        self._sort = state > 0

class DataTableBuffers(DataTable):
    def __init__(self, radioclient, G):
        super(DataTableBuffers, self).__init__(radioclient, G)
        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Buffer Full"] )

    def update(self):
        nodes = self.G.nodes();

        # get buffer fullness for all blocks
        kl = map(lambda x: "%s::%soutput %% full" % \
                 (x, self._statistics_table[self._statistic]),
                 nodes);
        buf_knobs = self.radioclient.getKnobs(kl)

        # strip values out of ctrlport response
        buffer_fullness = dict(zip(
            map(lambda x: x.split("::")[0], buf_knobs.keys()),
            map(lambda x: x.value, buf_knobs.values())))

        blockport_fullness = {}
        for blk in buffer_fullness:
            bdata = buffer_fullness[blk]
            if bdata:
                for port in range(0,len(bdata)):
                    blockport_fullness["%s:%d"%(blk,port)] =  bdata[port]

        if(self.perfTable.isVisible()):
            self.table_update(blockport_fullness);

        else:
            if(self._sort):
                sorted_fullness = sorted(blockport_fullness.iteritems(),
                                         key=operator.itemgetter(1))
                self._keymap = map(operator.itemgetter(0), sorted_fullness)
            else:
                if self._keymap:
                    sorted_fullness = len(self._keymap)*['',]
                    for b in blockport_fullness:
                        sorted_fullness[self._keymap.index(b)] = (b, blockport_fullness[b])
                else:
                    sorted_fullness = blockport_fullness.items()

            if(not self.disp):
                self.disp = self.sp.bar(range(0,len(sorted_fullness)),
                                        map(lambda x: x[1], sorted_fullness),
                                        alpha=0.5)
                self.sp.set_ylabel("% Buffers Full");
                self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_fullness))))
                self.sp.set_xticklabels(map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness)),
                                        rotation="vertical", verticalalignment="bottom")
            else:
                self.sp.set_xticklabels(map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness)),
                                        rotation="vertical", verticalalignment="bottom")
                for r,w in zip(self.disp, sorted_fullness):
                    r.set_height(w[1])

            self.f.canvas.draw()

class DataTableRuntimes(DataTable):
    def __init__(self, radioclient, G):
        super(DataTableRuntimes, self).__init__( radioclient, G)

    def update(self):
        nodes = self.G.nodes();

        # get work time for all blocks
        kl = map(lambda x: "%s::%swork time" % \
                 (x, self._statistics_table[self._statistic]),
                 nodes);
        wrk_knobs = self.radioclient.getKnobs(kl)

        # strip values out of ctrlport response
        total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
        if(total_work == 0):
            total_work = 1
        work_times = dict(zip(
            map(lambda x: x.split("::")[0], wrk_knobs.keys()),
            map(lambda x: 1e-10 + x.value/total_work, wrk_knobs.values())))

        # update table view
        if(self.perfTable.isVisible()):
            self.table_update(work_times)

        else:
            if(self._sort):
                sorted_work = sorted(work_times.iteritems(), key=operator.itemgetter(1))
                self._keymap = map(operator.itemgetter(0), sorted_work)
            else:
                if self._keymap:
                    sorted_work = len(self._keymap)*['',]
                    for b in work_times:
                        sorted_work[self._keymap.index(b)] = (b, work_times[b])
                else:
                    sorted_work = work_times.items()

            if(not self.disp):
                self.disp = self.sp.bar(range(0,len(sorted_work)),
                                        map(lambda x: x[1], sorted_work),
                                        alpha=0.5)
                self.sp.set_ylabel("% Runtime");
                self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_work))))
                self.sp.set_xticklabels( map(lambda x: "  " + x[0], sorted_work),
                                         rotation="vertical", verticalalignment="bottom" )
            else:
                self.sp.set_xticklabels( map(lambda x: "  " + x[0], sorted_work),
                                         rotation="vertical", verticalalignment="bottom" )
                for r,w in zip(self.disp, sorted_work):
                    r.set_height(w[1])

            self.f.canvas.draw()

class MForm(QtGui.QWidget):
    def update(self):
        try:
            try:
                # update current clock type
                self.prevent_clock_change = True;
                kl1 = None;
                if(self.clockKey == None):
                    kl1 = self.radioclient.getRe([".*perfcounter_clock"])
                else:
                    kl1 = self.radioclient.getKnobs([self.clockKey])
                self.clockKey = kl1.keys()[0]
                self.currClock = kl1[self.clockKey].value
                self.clockSelIdx = self.clocks.values().index(self.currClock)
                self.clockSel.setCurrentIndex(self.clockSelIdx)
                self.prevent_clock_change = False
            except:
                print("WARNING: Failed to get current clock setting!")

            nodes_stream = self.G_stream.nodes()
            nodes_msg = self.G_msg.nodes()

            # get current buffer depths of all output buffers
            kl = map(lambda x: "%s::%soutput %% full" % \
                     (x, self._statistics_table[self._statistic]),
                     nodes_stream);

            st = time.time()
            buf_knobs = self.radioclient.getKnobs(kl)
            td1 = time.time() - st;

            # strip values out of ctrlport response
            buf_vals = dict(zip(
                map(lambda x: x.split("::")[0], buf_knobs.keys()),
                map(lambda x: x.value, buf_knobs.values())))

            # get work time for all blocks
            kl = map(lambda x: "%s::%swork time" % \
                         (x, self._statistics_table[self._statistic]),
                     nodes_stream);
            st = time.time()
            wrk_knobs = self.radioclient.getKnobs(kl)
            td2 = time.time() - st;

            # strip values out of ctrlport response
            total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
            if(total_work == 0):
                total_work = 1
            work_times = dict(zip(
                        map(lambda x: x.split("::")[0], wrk_knobs.keys()),
                        map(lambda x: x.value/total_work, wrk_knobs.values())))
            work_times_padded = dict(zip(
                        self.G.nodes(),
                        [0.1]*len(self.G.nodes())))
            work_times_padded.update(work_times)

            for n in nodes_stream:
                # ne is the list of edges away from this node!
                ne = self.G.edges([n],True);
                #for e in ne: # iterate over edges from this block
                for e in ne: # iterate over edges from this block
                    # get the right output buffer/port weight for each edge
                    sourceport = e[2]["sourceport"];
                    if(e[2]["type"] == "stream"):
                        newweight = buf_vals[n][sourceport]
                        e[2]["weight"] = newweight;

            for n in nodes_msg:
                ne = self.G.edges([n],True);
                for e in ne: # iterate over edges from this block
                    sourceport = e[2]["sourceport"];
                    if(e[2]["type"] == "msg"):
                        newweight = 0.01;
                        e[2]["weight"] = newweight;

            # set updated weights
            #self.node_weights = map(lambda x: 20+2000*work_times[x], nodes_stream);
            self.node_weights = map(lambda x: 20+2000*work_times_padded[x], self.G.nodes());
            self.edge_weights = map(lambda x: 100.0*x[2]["weight"], self.G.edges(data=True));

            # draw graph updates
            if(self.do_update):
                self.drawGraph()
            else:
                self.updateGraph()

            latency = td1 + td2;
            self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%\
                                                    (latency*1000))

        except Exception as e:
            sys.stderr.write("gr-perf-monitorx: radio.getKnobs threw exception ({0}).\n".format(e))
            if(type(self.parent) is MAINWindow):
                # Find window of connection
                remove = []
                for p in self.parent.mdiArea.subWindowList():
                    if self.parent.conns[self.uid] == p.widget():
                        remove.append(p)

                # Remove subwindows for connection and plots
                for r in remove:
                    self.parent.mdiArea.removeSubWindow(r)

                # Clean up self
                self.close()
            else:
                sys.exit(1)
            return

    def rtt(self):
        self.parent.newSubWindow(  DataTableRuntimes(self.radioclient, self.G_stream),  "Runtime Table" );

    def bpt(self):
        self.parent.newSubWindow(  DataTableBuffers(self.radioclient, self.G_stream),  "Buffers Table" );

    def resetPCs(self):
        knobs = []
        for b in self.blocks_list:
            knobs += [self.radioclient.Knob(b + "::reset_perf_counters"),]
        k = self.radioclient.setKnobs(knobs)

    def toggleFlowgraph(self):
        if self.pauseFGAct.isChecked():
            self.pauseFlowgraph()
        else:
            self.unpauseFlowgraph()

    def pauseFlowgraph(self):
        knobs = [self.radioclient.Knob(self.top_block + "::lock"),
                 self.radioclient.Knob(self.top_block + "::stop")]
        k = self.radioclient.setKnobs(knobs)

    def unpauseFlowgraph(self):
        knobs = [self.radioclient.Knob(self.top_block + "::unlock")]
        k = self.radioclient.setKnobs(knobs)

    def stat_changed(self, index):
        self._statistic = str(self.stattype.currentText())

    def update_clock(self, clkidx):
        if(self.prevent_clock_change):
            return;
        idx = self.clockSel.currentIndex();
        clk = self.clocks.values()[idx]
#        print("UPDATE CLOCK!!! %d -> %d"%(idx,clk);)
        k = self.radioclient.getKnobs([self.clockKey]);
        k[self.clockKey].value = clk;
        km = {};
        km[self.clockKey] = k[self.clockKey];
        self.radioclient.setKnobs(km);

    def __init__(self, radioclient, uid=0, parent=None, dialogprompt = False):

        super(MForm, self).__init__()
        self.radioclient = radioclient
#         print("before radioclient.getHost()", radioclient.getHost(), radioclient.getPort(), "prompt", prompt)
        if(dialogprompt or radioclient.getHost() is None or radioclient.getPort() is None):
#             print("before ConInfoDialog")
            askinfo = ConInfoDialog(self);
            if askinfo.exec_():
                host = str(askinfo.host.text());
                port = str(askinfo.port.text());
#                 print("before radioclient.newConnection host: %s port: %s"%(host,port))
                newradio = self.radioclient.newConnection(host, port)
                if newradio is None:
                    print("Error making a %s connection to %s:%s from %s" % (radioclient.getName(), host, port, radioclient))
                else:
                    self.radioclient = newradio

            else:
                self.radioclient = None
                return

        self.uid = uid
        self.parent = parent

        self.layoutTop = QtGui.QVBoxLayout(self)
        self.ctlBox = QtGui.QHBoxLayout();
        self.layout = QtGui.QHBoxLayout()

        self.layoutTop.addLayout(self.ctlBox);
        self.layoutTop.addLayout(self.layout);

        self.rttAct = QtGui.QAction("Runtime Table",
                self, statusTip="Runtime Table", triggered=self.rtt)
        self.rttBut = Qt.QToolButton()
        self.rttBut.setDefaultAction(self.rttAct);
        self.ctlBox.addWidget(self.rttBut);

        self.bptAct = QtGui.QAction("Buffer Table",
                self, statusTip="Buffer Table", triggered=self.bpt)
        self.bptBut = Qt.QToolButton()
        self.bptBut.setDefaultAction(self.bptAct);
        self.ctlBox.addWidget(self.bptBut);

        self.resetPCsAct = QtGui.QAction("Reset", self,
                statusTip="Reset all Performance Counters",
                triggered=self.resetPCs)
        self.resetPCsBut = Qt.QToolButton()
        self.resetPCsBut.setDefaultAction(self.resetPCsAct);
        self.ctlBox.addWidget(self.resetPCsBut);

        self.pauseFGAct = QtGui.QAction("Pause", self,
                statusTip="Pause the Flowgraph",
                triggered=self.toggleFlowgraph)
        self.pauseFGAct.setCheckable(True)
        self.pauseFGBut = Qt.QToolButton()
        self.pauseFGBut.setDefaultAction(self.pauseFGAct);
        self.ctlBox.addWidget(self.pauseFGBut);

        self.prevent_clock_change = True;
        self.clockKey = None;
        self.clocks = {"MONOTONIC":1, "THREAD":3};
        self.clockSel = QtGui.QComboBox(self);
        map(lambda x: self.clockSel.addItem(x), self.clocks.keys());
        self.ctlBox.addWidget(self.clockSel);
        self.clockSel.currentIndexChanged.connect(self.update_clock);
        self.prevent_clock_change = False;

        self._statistic = "Instantaneous"
        self._statistics_table = {"Instantaneous": "",
                                  "Average": "avg ",
                                  "Variance": "var "}
        self.stattype = QtGui.QComboBox()
        self.stattype.addItem("Instantaneous")
        self.stattype.addItem("Average")
        self.stattype.addItem("Variance")
        self.stattype.setMaximumWidth(200)
        self.ctlBox.addWidget(self.stattype);
        self.stattype.currentIndexChanged.connect(self.stat_changed)

#        self.setLayout(self.layout);

        self.radio = radioclient
        self.knobprops = self.radio.properties([])
        self.parent.knobprops.append(self.knobprops)

        self.timer = QtCore.QTimer()
        self.constupdatediv = 0
        self.tableupdatediv = 0
        plotsize=250


        # Set up the graph of blocks
        input_name = lambda x: x+"::avg input % full"
        output_name = lambda x: x+"::avg output % full"
        wtime_name = lambda x: x+"::avg work time"
        nout_name = lambda x: x+"::avg noutput_items"
        nprod_name = lambda x: x+"::avg nproduced"

        tmplist = []
        knobs = self.radio.getKnobs([])
        edgelist = None
        msgedgelist = None
        for k in knobs:
            propname = k.split("::")
            blockname = propname[0]
            keyname = propname[1]
            if(keyname == "edge list"):
                edgelist = knobs[k].value
                self.top_block = blockname
            elif(keyname == "msg edges list"):
                msgedgelist = knobs[k].value
            elif(blockname not in tmplist):
                # only take gr_blocks (no hier_block2)
                if(knobs.has_key(input_name(blockname))):
                    tmplist.append(blockname)


        if not edgelist:
            sys.stderr.write("Could not find list of edges from flowgraph. " + \
                                 "Make sure the option 'edges_list' is enabled " + \
                                 "in the ControlPort configuration.\n\n")
            sys.exit(1)

        self.blocks_list = tmplist
        edges = edgelist.split("\n")[0:-1]
        msgedges = msgedgelist.split("\n")[0:-1]

        edgepairs_stream = [];
        edgepairs_msg = [];

        # add stream connections
        for e in edges:
            _e = e.split("->")
            edgepairs_stream.append( (_e[0].split(":")[0], _e[1].split(":")[0],
                               {"type":"stream", "sourceport":int(_e[0].split(":")[1])}) );

        # add msg connections
        for e in msgedges:
            _e = e.split("->")
            edgepairs_msg.append( (_e[0].split(":")[0], _e[1].split(":")[0],
                               {"type":"msg", "sourceport":_e[0].split(":")[1]}) );

        self.G = nx.MultiDiGraph();
        self.G_stream = nx.MultiDiGraph();
        self.G_msg = nx.MultiDiGraph();

        self.G.add_edges_from(edgepairs_stream);
        self.G.add_edges_from(edgepairs_msg);

        self.G_stream.add_edges_from(edgepairs_stream);
        self.G_msg.add_edges_from(edgepairs_msg);

        n_edges = self.G.edges(data=True);
        for e in n_edges:
            e[2]["weight"] = 5+random.random()*10;

        self.G.clear();
        self.G.add_edges_from(n_edges);


        self.f = plt.figure(figsize=(10,8), dpi=90)
        self.sp = self.f.add_subplot(111);
        self.sp.autoscale_view(True,True,True);
        self.sp.set_autoscale_on(True)

        self.layout.addWidget(self.f.canvas);

        self.pos = graphviz_layout(self.G);
        #self.pos = pygraphviz_layout(self.G);
        #self.pos = nx.spectral_layout(self.G);
        #self.pos = nx.circular_layout(self.G);
        #self.pos = nx.shell_layout(self.G);
        #self.pos = nx.spring_layout(self.G);

        # Indicate to self.update to initialize the graph
        self.do_update = False

        # generate weights and plot
        self.update();

        # set up timer
        self.timer = QtCore.QTimer()
        self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
        self.timer.start(1000)

        # Set up mouse callback functions to move blocks around.
        self._grabbed = False
        self._current_block = ''
        self.f.canvas.mpl_connect('button_press_event',
                                  self.button_press)
        self.f.canvas.mpl_connect('motion_notify_event',
                                  self.mouse_move)
        self.f.canvas.mpl_connect('button_release_event',
                                  self.button_release)

    def button_press(self, event):
        x, y = event.xdata, event.ydata
        thrsh = 100

        if(x is not None and y is not None):
            nearby = map(lambda z: math.sqrt( math.pow(x-z[0],2) + math.pow(y-z[1],2)), self.pos.values())
            i = nearby.index(min(nearby))
            if(abs(self.pos.values()[i][0] - x) < thrsh and
               abs(self.pos.values()[i][1]-y) < thrsh):
                self._current_block = self.pos.keys()[i]
                #print "MOVING BLOCK: ", self._current_block
                #print "CUR POS: ", self.pos.values()[i]
                self._grabbed = True

    def mouse_move(self, event):
        if self._grabbed:
            x, y = event.xdata, event.ydata
            if(x is not None and y is not None):
                self.pos[self._current_block] = (x,y)
                self.updateGraph()

    def button_release(self, event):
        self._grabbed = False


    def openMenu(self, pos):
        index = self.table.treeWidget.selectedIndexes()
        item = self.table.treeWidget.itemFromIndex(index[0])
        itemname = str(item.text(0))
        self.parent.propertiesMenu(itemname, self.radioclient, self.uid)

    def drawGraph(self):

        self.do_update = True
        self.f.canvas.updateGeometry()
        self.sp.clear()
        plt.figure(self.f.number)
        plt.subplot(111)
        nx.draw(self.G, self.pos,
                edge_color=self.edge_weights,
                node_color='#A0CBE2',
                width=map(lambda x: 3+math.log(x+1e-20), self.edge_weights),
                node_shape="s",
                node_size=self.node_weights,
                edge_cmap=plt.cm.Reds,
                ax=self.sp,
                arrows=False
        )
        nx.draw_networkx_labels(self.G, self.pos,
                                font_size=12)

        self.f.canvas.show()

    def updateGraph(self):

        self.sp.clear()
        nx.draw_networkx_nodes(self.G, self.pos,
                               node_color='#A0CBE2',
                               node_shape="s",
                               node_size=self.node_weights,
                               ax=self.sp,
                               arrows=False)

        nx.draw_networkx_edges(self.G, self.pos,
                               edge_color=self.edge_weights,
                               width=map(lambda x: 3+math.log(x+1e-20), self.edge_weights),
                               edge_cmap=plt.cm.Reds,
                               ax=self.sp,
                               arrows=False)

        nx.draw_networkx_labels(self.G, self.pos,
                                ax=self.sp, font_size=12)

        self.f.canvas.draw()


class MyApp(object):
    def __init__(self, args):
        p = gr.prefs()
        cp_on = p.get_bool("ControlPort", "on", False)
        cp_edges = p.get_bool("ControlPort", "edges_list", False)
        pcs_on = p.get_bool("PerfCounters", "on", False)
        pcs_exported = p.get_bool("PerfCounters", "export", False)
        if(not (pcs_on and cp_on and pcs_exported and cp_edges)):
            print("Configuration has not turned on all of the appropriate ControlPort features:")
            print("\t[ControlPort] on = {0}".format(cp_on))
            print("\t[ControlPort] edges_list = {0}".format(cp_edges))
            print("\t[PerfCounters] on = {0}".format(pcs_on))
            print("\t[PerfCounters] export = {0}".format(pcs_exported))
            exit(1)

        from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
        GNURadioControlPortClient(args, 'thrift', self.run, QtGui.QApplication(sys.argv).exec_)

    def run(self, client):
        MAINWindow(client).show()

MyApp(sys.argv)
